package com.hup.utils.swing.customView;

import com.hup.utils.algorithm.image.GrayWithAlpha;
import com.hup.utils.algorithm.image.ImageShape;
import com.hup.utils.commons.exception.AppExceptionHandler;
import com.hup.utils.commons.hardware.MouseUtil;
import com.hup.utils.commons.start.LookThemeUtil;
import com.hup.utils.swing.ColorUtil;
import com.hup.utils.swing.GraphicsUtil;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.log4j.Log4j2;
import sun.swing.SwingUtilities2;

import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.KeyStroke;
import java.awt.AlphaComposite;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Composite;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.util.HashMap;

/**
 * 易用的[图标按钮],支持缩放比例;
 *
 * @author hugan
 * @apiNote 与修改swing原生控件来实现的ImageButton的方式相比, 本类思路清晰(但完善后续功能后也会变复杂), 没有swing各种隐藏坑, 没有繁琐的配置参数
 * @date 2020/4/14
 */
@Log4j2
@SuppressWarnings("DuplicatedCode")
public class ImageButton extends JLabel {

    /**
     * {@link #shareIconRes}
     */
    @Getter
    private static final HashMap<String, ButtonImages> SHARE_IMAGES = new HashMap<>();

    /**
     * {@link #fillTransparentBg},实现的关键是alpha:不能是0,而且要'透明',所以取1
     */
    private static final Color TRANSPARENT_BG_COLOR = new Color(255, 255, 255, 1);
    /**
     * 图标尺寸单位:以默认字体的高度作为一个单位
     */
    public static final int SIZE_UNIT = SwingUtilities2.getFontMetrics(null, LookThemeUtil.getDefaultScaledFont()).getHeight();

    /**
     * 图标图案距离(最近)边缘的padding
     */
    @Setter
    @Getter
    private int padding = 5;

    /**
     * 共用形图标的res路径;
     * .格式如:"/img/icon_dir.png"
     * .使用 {@link #SHARE_IMAGES} 进行图标缓存
     * .本字段是为了支持idea的GUI design中设置和预览图标而用的, {@link #setIcon(ButtonImages)}
     */
    private String shareIconRes;
    /**
     * 图标的宽高比例=w/h
     */
    private float iconRatio;
    /**
     * 原始图标
     */
    private Image icon;
    /**
     * 灰度图[复选框模式下不选中,enable=false]时的图像;
     */
    private Image iconGray;
    /**
     * true-任何情况下都使用彩色图标
     */
    @Setter
    @Getter
    private boolean alwaysColored;
    /**
     * 鼠标进入后的图标-仅轮廓,无原图
     */
    private Image shapeImg;
    /**
     * 轮廓描边的颜色
     */
    @Getter
    private Color shapeColor = Color.CYAN;

    private boolean mouseIn, pressed;
    private boolean focused;
    @Getter
    private boolean selected;

    @Setter
    @Getter
    private ActionListener actionListener;

    @Setter
    @Getter
    private String actionCommand;

    @Setter
    @Getter
    private Color mouseInColor = ColorUtil.DEF_MOUSE_IN_COLOR;
    @Setter
    @Getter
    private Color selectedColor = ColorUtil.DEF_SELECTED_COLOR;

    /**
     * true-复选框(用于处理点击后切换选中状态);false-普通button
     */
    @Setter
    @Getter
    private boolean isCheckbox;
    /**
     * 图标尺寸(含四边padding):以 {@link #SIZE_UNIT} 作为单位的;
     * 默认宽高都是[1.5个字的高度];
     * 没有文本时,等于组件尺寸
     */
    @Getter
    private float unitedIconWidth = 1.5f;
    @Getter
    private float unitedIconHeight = 1.5f;

    /**
     * 设置整个按钮背景是"全透明"的效果:设置为true时,原有的背景色将不背绘制!(二选一)
     * 使用场景:窗口是[透明像素可穿透]模式下,使按钮的'触摸区域'铺满整个按钮,而不是仅在图标上
     */
    @Setter
    @Getter
    private boolean fillTransparentBg;
    /**
     * {@link JComponent#setBackground(Color)};
     * 本类支持设置背景色,但要生效则需要同时 {@link #setOpaque(boolean)}=true
     */
    @Setter
    private Color background;

    /**
     * true-使用[共用的,复用的,static的]图标,在修改图标时,不flush旧图标;
     * 默认为false
     */
    @Setter
    @Getter
    private boolean useSharedIcon = false;
    /**
     * 按钮文字; {@link #setTextPosition(TextPosition)}
     */
    @Getter
    private String text = null;
    /**
     * 文字文字;默认在图标底部
     */
    @Getter
    private TextPosition textPosition = TextPosition.BOTTOM;
    /**
     * 设置文本/字体/图标尺寸后都更新的文本尺寸
     */
    private Dimension textSize = new Dimension();

    /**
     * 类似安卓的view.tag
     */
    @Setter
    @Getter
    private Object tag;

    public ImageButton() {
        setFocusable(true);
        setText(null);
        setUnitedIconSize(unitedIconWidth, unitedIconHeight);
        initListener();
    }

    private void initListener() {
        MouseAdapter ma = new MouseAdapter() {
            @Override
            public void mousePressed(MouseEvent e) {
                if (!isEnabled()) return;
                requestFocus();
                if (MouseUtil.isLeftButton(e)) {
                    pressed = true;
                    repaint();
                }
            }

            @Override
            public void mouseReleased(MouseEvent e) {
                if (!isEnabled()) return;
                if (MouseUtil.isLeftButton(e)) {
                    if (MouseUtil.isInsideComponent(ImageButton.this, e)) {
                        if (isCheckbox) {
                            selected = !selected;
                        }
                        if (actionListener != null) {
                            actionListener.actionPerformed(new ActionEvent(ImageButton.this, ActionEvent.ACTION_PERFORMED, actionCommand));
                        }
                    }
                    pressed = false;
                    repaint();
                }

            }

            @Override
            public void mouseEntered(MouseEvent e) {
                mouseIn = true;
                repaint();
            }

            @Override
            public void mouseExited(MouseEvent e) {
                mouseIn = false;
                repaint();
            }
        };
        addMouseListener(ma);
        addFocusListener(new FocusAdapter() {
            @Override
            public void focusGained(FocusEvent e) {
                focused = true;
                repaint();
            }

            @Override
            public void focusLost(FocusEvent e) {
                focused = false;
                repaint();
            }
        });
        //回车/空格触发按下事件
        ActionListener keyActionListener = e -> {
            //直接模拟鼠标事件:处理checkbox等功能
            MouseEvent event = new MouseEvent(this, MouseEvent.MOUSE_RELEASED, System.currentTimeMillis(), 0,
                    1, 1,
                    1, false, MouseEvent.BUTTON1);
            ma.mouseReleased(event);
        };
        registerKeyboardAction(keyActionListener, KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, 0), JComponent.WHEN_FOCUSED);
        registerKeyboardAction(keyActionListener, KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), JComponent.WHEN_FOCUSED);
        //取消本组件焦点
        //registerKeyboardAction(e -> transferFocus(), KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_FOCUSED);
    }

    public void setImage(Image icon) {
        if (this.icon != null) {
            if (!useSharedIcon) this.icon.flush();
            iconGray.flush();
            shapeImg.flush();
        }
        ButtonImages images = new ButtonImages(icon, shapeColor);
        this.icon = images.icon;
        this.iconRatio = images.iconRatio;
        this.iconGray = images.iconGray;
        this.shapeImg = images.shapeImg;
    }

    @Override
    public void paint(Graphics g0) {
        Graphics2D g = GraphicsUtil.antiAlias((Graphics2D) g0);
        int width = getWidth();
        int height = getHeight();
        if (width == 0 || height == 0) return;

        if (isOpaque() || fillTransparentBg) {
            //不透明,或 fillTransparentBg
            Color bgColor = fillTransparentBg ? TRANSPARENT_BG_COLOR : background;

            //默认是SRC_OVER:绘制时将当前颜色,叠加在原有颜色上
            Composite defComposite = g.getComposite();
            g.setComposite(AlphaComposite.Src);//直接将像素点,设为当前颜色
            if (bgColor == null) {
                bgColor = super.getBackground();
            }
            if (bgColor != null) {
                g.setColor(bgColor);
                g.fillRect(0, 0, width, height);
            }
            g.setComposite(defComposite);
        } else {
            //默认绘制方法
            //super.paint(g);//会导致drawString残留??
        }
        //log.info("tip={}, mouseIn={}, selected={}", getToolTipText(), mouseIn, selected);

        //背景色
        Color bgC = null;
        if (!isEnabled()) {
            if (mouseIn) bgC = mouseInColor;
        } else {
            if (pressed || selected) {
                bgC = selectedColor;
            } else if (mouseIn) {
                bgC = mouseInColor;
            }
        }
        if (bgC != null) {
            g.setColor(bgC);
            g.fillRect(0, 0, width, height);
        }

        //图标
        //图标的最大长宽
        int iconW = width - padding * 2;//无文字时的尺寸
        int iconH = height - padding * 2;
        if (text != null) {//修正文字后的尺寸
            if (textPosition == TextPosition.TOP || textPosition == TextPosition.BOTTOM) {
                iconH -= textSize.height;
            } else if (textPosition == TextPosition.LEFT || textPosition == TextPosition.RIGHT) {
                iconW -= textSize.width;
            }
        }
        //处理<=0
        if (iconW <= 0) iconW = 1;//避免无图像,或分母为零
        if (iconH <= 0) iconH = 1;
        //修正比例后的尺寸
        double rParent = iconW / (iconH + 0d);
        if (rParent < iconRatio) {
            iconH = (int) (iconW / iconRatio);
        } else {
            iconW = (int) (iconH * iconRatio);
        }
        //计算图标位置,绘制文字
        int iconX = (width - iconW) / 2;
        int iconY = (height - iconH) / 2;
        if (text != null) {
            int textX = 0;
            int textY = 0;
            FontMetrics textMetrics = getFontMetrics(getFont());
            int leading = textMetrics.getLeading();
            int ascent = textMetrics.getAscent();
            switch (textPosition) {
                case TOP:
                    iconY = (height - iconH - textSize.height) / 2 + textSize.height;
                    textX = (width - textSize.width) / 2;
                    textY = (height - iconH - textSize.height) / 2 + leading + ascent;
                    break;
                case BOTTOM:
                    iconY = (height - iconH - textSize.height) / 2;
                    textX = (width - textSize.width) / 2;
                    textY = iconY + iconH + ascent;
                    break;
                case LEFT:
                    textX = (width - iconW - textSize.width - padding) / 2;
                    iconX = textX + textSize.width + padding;//padding是图标与文字之间的间隔
                    textY = (height - textSize.height) / 2 + ascent;
                    break;
                case RIGHT:
                    iconX = (width - iconW - textSize.width - padding) / 2;
                    textX = iconX + iconW + padding;
                    textY = (height - textSize.height) / 2 + ascent;
                    break;
            }
            g.setFont(getFont());
            g.setColor(getForeground());
            g.drawString(text, textX, textY);
        }
        Rectangle iconRect = new Rectangle(iconX, iconY, iconW, iconH);
        Image bgImage;
        if (!isCheckbox) {
            bgImage = isEnabled() ? icon : iconGray;
        } else {
            bgImage = selected || alwaysColored ? icon : iconGray;
        }
        g.drawImage(bgImage, iconRect.x, iconRect.y, iconRect.width, iconRect.height, null);

        //轮廓
        if (isEnabled() && (mouseIn || focused)) {
            g.drawImage(shapeImg, iconRect.x, iconRect.y, iconRect.width, iconRect.height, null);
        }
    }

    /**
     * {@link #unitedIconWidth}
     */
    public void setUnitedIconWidth(float unitedIconWidth) {
        setUnitedIconSize(unitedIconWidth, unitedIconHeight);
    }

    /**
     * {@link #unitedIconWidth}
     */
    public void setUnitedIconHeight(float unitedIconHeight) {
        setUnitedIconSize(unitedIconWidth, unitedIconHeight);
    }

    /**
     * {@link #unitedIconWidth}
     */
    public void setUnitedIconSize(float unitedIconWidth, float unitedIconHeight) {
        this.unitedIconWidth = unitedIconWidth;
        this.unitedIconHeight = unitedIconHeight;
        updateSize();
    }

    private void updateSize() {
        if (getFont() == null || textSize == null) return;//父类初始化时,变量为空
        Dimension size = new Dimension((int) (SIZE_UNIT * unitedIconWidth), (int) (SIZE_UNIT * unitedIconHeight));
        if (text != null) {
            FontMetrics metricsText = getFontMetrics(getFont());
            textSize.width = metricsText.stringWidth(text);
            textSize.height = metricsText.getHeight();
            if (textPosition == TextPosition.TOP || textPosition == TextPosition.BOTTOM) {
                int textMinWidth = textSize.width + padding * 2;//字体占用最小宽度=字宽+两边padding
                if (size.width < textMinWidth) size.width = textMinWidth;
                size.height += textSize.height;
            } else if (textPosition == TextPosition.LEFT || textPosition == TextPosition.RIGHT) {
                int temp = textSize.height + padding * 2;//字体占用最小高度
                if (size.height < temp) size.height = temp;
                size.width += textSize.width + padding;//+=字宽+图标与文字的间距
            }
        } else {
            textSize.width = textSize.height = 0;
        }
        setPreferredSize(size);
        setMinimumSize(size);

        revalidate();
        repaint();
    }

    @Override
    public void setIcon(Icon icon) {
        if (icon == null) {
            //构造时会传入null
            this.icon = null;
        } else if (icon instanceof ImageIcon) {
            setImage(((ImageIcon) icon).getImage());
        } else {
            throw new IllegalArgumentException("未支持此类型:" + icon.getClass().getName());
        }
    }

    /**
     * 设置图标,图标是全局共用的;
     * 使用场景listView中的item,
     * .解决:listView中有很多个ImageButton使用独立的setIcon时, 创建view时会很耗时
     *
     * @param images {@link ButtonImages}
     * @see #setIcon(Icon)
     */
    public void setIcon(ButtonImages images) {
        useSharedIcon = true;
        this.icon = images.icon;
        this.iconRatio = images.iconRatio;
        this.iconGray = images.iconGray;
        this.shapeImg = images.shapeImg;
        this.shapeColor = images.shapeColor;
    }

    /**
     * {@link #setIcon(ButtonImages)}
     */
    public void setShareIcon(String iconRes) {
        this.shareIconRes = iconRes;
        ButtonImages images = SHARE_IMAGES.computeIfAbsent(iconRes, s -> {
            try {
                return new ButtonImages(iconRes, shapeColor);
            } catch (Exception e) {
                log.error("图标加载异常={}", iconRes);
                return ButtonImages.NOT_FOUND;
            }
        });
        setIcon(images);
    }

    public String getShareIcon() {
        return shareIconRes;
    }

    @Override
    public void setText(String text) {
        this.text = text;
        updateSize();
    }

    public void setTextPosition(TextPosition textPosition) {
        this.textPosition = textPosition;
        updateSize();
    }

    @Override
    public void setFont(Font font) {
        super.setFont(font);
        updateSize();
    }

    public void setShapeColor(Color shapeColor) {
        this.shapeColor = shapeColor;
        if (icon != null) setImage(icon);
    }

    public void setSelected(boolean selected) {
        this.selected = selected;
        repaint();
    }

    public enum TextPosition {
        TOP, BOTTOM, LEFT, RIGHT
    }

    /**
     * {@link ImageButton} 的图标数据
     */
    public static class ButtonImages {
        private static final ButtonImages NOT_FOUND = new ButtonImages();
        private Image icon;
        private Color shapeColor;
        private float iconRatio;
        private Image iconGray;
        private Image shapeImg;

        private ButtonImages() {
            shapeColor = Color.CYAN;
        }

        /**
         * @param iconRes 如:"/img/icon_dir.png"
         */
        public ButtonImages(String iconRes, Color shapeColor) {
            this(new ImageIcon(ButtonImages.class.getResource(iconRes)).getImage(), shapeColor);
        }

        /**
         * 轮廓描边的颜色
         */
        public ButtonImages(Image icon, Color shapeColor) {
            this.icon = icon;
            this.shapeColor = shapeColor;
            //绘制轮廓图像,描边直接覆盖在原图边缘像素
            Shape iconShape = ImageShape.getImageShape(this.icon);
            int iconWidth = this.icon.getWidth(null);
            int iconHeight = this.icon.getHeight(null);
            iconRatio = (float) (iconWidth / (iconHeight + 0d));
            //
            try {
                iconGray = GrayWithAlpha.toGray(icon);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                AppExceptionHandler.handle("灰度图获取异常?:", e);
                iconGray = icon;
            }
            //
            BufferedImage bf = new BufferedImage(iconWidth, iconHeight, BufferedImage.TYPE_INT_ARGB);
            Graphics2D g = bf.createGraphics();
            GraphicsUtil.antiAlias(g);
            g.setColor(shapeColor);
            g.setStroke(new BasicStroke(2));
            g.draw(iconShape);
            g.dispose();
            this.shapeImg = bf;
        }

    }

}
